 /*******************************************************************************
  * Copyright (c) 2002, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.cheatsheets.views;

 import java.net.MalformedURLException ;
 import java.net.URL ;
 import java.util.ArrayList ;
 import java.util.Enumeration ;
 import java.util.Hashtable ;
 import java.util.Iterator ;
 import java.util.ListIterator ;
 import java.util.Map ;
 import java.util.Properties ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.help.ui.internal.views.HelpTray;
 import org.eclipse.help.ui.internal.views.IHelpPartPage;
 import org.eclipse.help.ui.internal.views.ReusableHelpPart;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.dialogs.TrayDialog;
 import org.eclipse.jface.window.Window;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.graphics.Cursor;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Widget;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.cheatsheets.CheatSheetListener;
 import org.eclipse.ui.cheatsheets.ICheatSheetEvent;
 import org.eclipse.ui.cheatsheets.ICheatSheetViewer;
 import org.eclipse.ui.forms.widgets.FormToolkit;
 import org.eclipse.ui.forms.widgets.ImageHyperlink;
 import org.eclipse.ui.internal.cheatsheets.CheatSheetPlugin;
 import org.eclipse.ui.internal.cheatsheets.CheatSheetStopWatch;
 import org.eclipse.ui.internal.cheatsheets.ICheatSheetResource;
 import org.eclipse.ui.internal.cheatsheets.Messages;
 import org.eclipse.ui.internal.cheatsheets.actions.IMenuContributor;
 import org.eclipse.ui.internal.cheatsheets.composite.model.CompositeCheatSheetModel;
 import org.eclipse.ui.internal.cheatsheets.composite.views.CompositeCheatSheetPage;
 import org.eclipse.ui.internal.cheatsheets.data.CheatSheet;
 import org.eclipse.ui.internal.cheatsheets.data.CheatSheetParser;
 import org.eclipse.ui.internal.cheatsheets.data.CheatSheetSaveHelper;
 import org.eclipse.ui.internal.cheatsheets.data.ICheatSheet;
 import org.eclipse.ui.internal.cheatsheets.data.IParserTags;
 import org.eclipse.ui.internal.cheatsheets.data.ParserInput;
 import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetElement;
 import org.eclipse.ui.internal.cheatsheets.registry.CheatSheetRegistryReader;
 import org.eclipse.ui.internal.cheatsheets.state.DefaultStateManager;
 import org.eclipse.ui.internal.cheatsheets.state.ICheatSheetStateManager;
 import org.eclipse.ui.internal.cheatsheets.state.NoSaveStateManager;
 import org.eclipse.ui.internal.cheatsheets.state.TrayStateManager;
 import org.osgi.framework.Bundle;

 public class CheatSheetViewer implements ICheatSheetViewer, IMenuContributor {

     //CS Elements
 private CheatSheetElement contentElement;
     private ParserInput parserInput;
     private String currentID;
     private int currentItemNum;
     // Used to indicate if an invalid cheat sheet id was specified via setInput.
 private boolean invalidCheatSheetId = false;
     // Used to indicate if a null cheat sheet id was specified via setInput.
 private boolean nullCheatSheetId = false;

     private CheatSheetParser parser;
     private ICheatSheet model;
     private CheatSheetManager manager;
     private CheatSheetSaveHelper saveHelper;

     private CheatSheetExpandRestoreAction expandRestoreAction;
     private Action copyAction;

     //ITEMS
 private ViewItem currentItem;

     //Lists
 private ArrayList expandRestoreList = new ArrayList ();
     private ArrayList viewItemList = new ArrayList ();

     //Composites
 protected Composite control;

     private Cursor busyCursor;
     
     // The page currently displayed, may be a CheatSheetPage, CompositeCheatSheetPage
 // or ErrorPage
 private Page currentPage;
     private Label howToBegin;
     private boolean inDialog;
     private Listener listener;
     
     private ICheatSheetStateManager stateManager; // The state manager to use when saving
 private ICheatSheetStateManager preTrayManager; // The state manager in use before a tray was opened
 private String restorePath;
     
     private int dialogReturnCode;
     private boolean isRestricted;
     
     /**
      * The constructor.
      *
      * @param inDialog whether or not this viewer will be placed in a modal dialog
      */
     public CheatSheetViewer(boolean inDialog) {
         currentItemNum = -1;
         this.inDialog = inDialog;
         saveHelper = new CheatSheetSaveHelper();
     }

     public void advanceIntroItem() {
         resetItemState();
         /* LP-item event */
         // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_DEACTIVATED, introItem);

         currentItemNum = 1;
         ViewItem nextItem = getViewItemAtIndex(currentItemNum);
         if (nextItem.item.isDynamic()) {
             nextItem.handleButtons();
         }
         nextItem.setAsCurrentActiveItem();
         /* LP-item event */
         // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, nextItem);
 collapseAllButCurrent(false);
         
         saveCurrentSheet();
     }

     /**
      * Reset the state of all the items in this cheatsheet
      */
     private void resetItemState() {
         IntroItem introItem = (IntroItem) getViewItemAtIndex(0);
         boolean isStarted = introItem.isCompleted();

         expandRestoreList = new ArrayList ();
         if(expandRestoreAction != null)
             expandRestoreAction.setCollapsed(false);

         clearBackgrounds();
         clearIcons();
         collapseAllButtons();
         if(isStarted)
             initManager();

         for (Iterator iter = viewItemList.iterator(); iter.hasNext();) {
             ViewItem item = (ViewItem) iter.next();
             if (item instanceof CoreItem) {
                 CoreItem c = (CoreItem) item;
                 ArrayList l = c.getListOfSubItemCompositeHolders();
                 if (l != null)
                     for (int j = 0; j < l.size(); j++) {
                         ((SubItemCompositeHolder) l.get(j)).setSkipped(false);
                         ((SubItemCompositeHolder) l.get(j)).setCompleted(false);
                     }
             }
         }

         if (isStarted)
             getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_RESTARTED);
         else
             getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_STARTED);

         isStarted = true;
         introItem.setAsNormalCollapsed();
         introItem.setComplete();
         introItem.setRestartImage();
     }

     /*package*/
     /*
      * This function can do one of three things
      * 1. If this item has a completion message which has not been displayed, display it
      * 2. Otherwise if this is the final item return to the introduction
      * 3. If neither condition 1 or 2 is satisfied move to the next item
      */
     void advanceItem(ImageHyperlink link, boolean markAsCompleted) {
         currentItem = (ViewItem) link.getData();
         int indexNextItem = getIndexOfItem(currentItem) +1;
         boolean isFinalItem = indexNextItem >= viewItemList.size();
         
         if (markAsCompleted
                 && currentItem.hasCompletionMessage()
                 && !currentItem.isCompletionMessageExpanded()) {
             currentItem.setCompletionMessageExpanded(isFinalItem);
             currentItem.setComplete();
             if (isFinalItem) {
                 getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_COMPLETED);
             }
             saveCurrentSheet();
             return;
         }

         if (indexNextItem < currentItemNum) {
             ViewItem vi = getViewItemAtIndex(currentItemNum);
             vi.setAsNormalNonCollapsed();
         }
         if (currentItem != null) {
             //set that item to it's original color.
 currentItem.setAsNormalCollapsed();
             //set that item as complete.
 if (markAsCompleted) {
                 if (!currentItem.isCompleted()) {
                     currentItem.setComplete();
                 }
                 /* LP-item event */
                 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_COMPLETED, currentItem);
 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_DEACTIVATED, currentItem);
 } else {
                 currentItem.setSkipped();
                 /* LP-item event */
                 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_SKIPPED, currentItem);
 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_DEACTIVATED, currentItem);
 }
         }
         if (!isFinalItem) {
             ViewItem nextItem = getViewItemAtIndex(indexNextItem);
             currentItemNum = indexNextItem;
             if (nextItem != null) {
                 //Handle lazy button instantiation here.
 if (nextItem.item.isDynamic()) {
                     ((CoreItem) nextItem).handleButtons();
                 }
                 nextItem.setAsCurrentActiveItem();
                 /* LP-item event */
                 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, nextItem);
 currentItem = nextItem;
             }

             FormToolkit.ensureVisible(currentItem.getMainItemComposite());
         } else if (indexNextItem == viewItemList.size()) {
             if (!currentItem.isCompletionMessageExpanded()) { // The event will already have been fired
 getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_COMPLETED);
             }
             showIntroItem();
         }

         saveCurrentSheet();
     }

     private void showIntroItem() {
         ViewItem item = getViewItemAtIndex(0);
         item.setAsCurrentActiveItem();
     }

     /*package*/ void advanceSubItem(ImageHyperlink link, boolean markAsCompleted, int subItemIndex) {
         Label l = null;
         ArrayList list = null;
         SubItemCompositeHolder sich = null;
         CoreItem ciws = null;

         currentItem = (ViewItem) link.getData();

         if (currentItem instanceof CoreItem)
             ciws = (CoreItem) currentItem;

         if (ciws != null) {
             list = ciws.getListOfSubItemCompositeHolders();
             sich = (SubItemCompositeHolder) list.get(subItemIndex);
             l = sich.getCheckDoneLabel();
         }

         if (l != null) {
             if (markAsCompleted) {
                 sich.setCompleted(true);
                 sich.setSkipped(false);
                 /* LP-subitem event */
                 // fireManagerSubItemEvent(ICheatSheetItemEvent.ITEM_COMPLETED, ciws, subItemID);
 } else {
                 sich.setSkipped(true);
                 sich.setCompleted(false);
                 /* LP-subitem event */
                 // fireManagerSubItemEvent(ICheatSheetItemEvent.ITEM_SKIPPED, ciws, subItemID);
 }
             ciws.refreshItem();
         }

         boolean allAttempted = checkAllAttempted(list);
         boolean anySkipped = checkContainsSkipped(list);

         if (allAttempted && !anySkipped) {
             advanceItem(link, true);
             return;
         } else if (allAttempted && anySkipped) {
             advanceItem(link, false);
             return;
         }
         
         setFocus();
         saveCurrentSheet();
     }

     private boolean checkAllAttempted(ArrayList list) {
         for (int i = 0; i < list.size(); i++) {
             SubItemCompositeHolder s = (SubItemCompositeHolder) list.get(i);
             if (s.isCompleted() || s.isSkipped()) {
                 continue;
             }
             return false;
         }
         return true;
     }

     private boolean checkContainsSkipped(ArrayList list) {
         for (int i = 0; i < list.size(); i++) {
             SubItemCompositeHolder s = (SubItemCompositeHolder) list.get(i);
             if (s.isSkipped()) {
                 return true;
             }
         }
         return false;
     }

     private boolean loadState() {
         try {
             Properties props = stateManager.getProperties();

             manager = stateManager.getCheatSheetManager();
     
             // There is a bug which causes the background of the buttons to
 // remain white, even though the color is set. So instead of calling
 // clearBackgrounds() only the following line should be needed. D'oh!
 // ((ViewItem) viewItemList.get(0)).setOriginalColor();
 clearBackgrounds();
     
             if (props == null) {
                 getViewItemAtIndex(0).setAsCurrentActiveItem();
                 /* LP-item event */
                 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, items[0]);
 return true;
             }
     
             boolean buttonIsDown = (Integer.parseInt((String ) props.get(IParserTags.BUTTON)) == 0) ? false : true;
             int itemNum = Integer.parseInt((String ) props.get(IParserTags.CURRENT));
             ArrayList completedStatesList = (ArrayList ) props.get(IParserTags.COMPLETED);
             ArrayList expandedStatesList = (ArrayList ) props.get(IParserTags.EXPANDED);
             expandRestoreList = (ArrayList ) props.get(IParserTags.EXPANDRESTORE);
             String cid = (String ) props.get(IParserTags.ID);
             Hashtable completedSubItems = (Hashtable ) props.get(IParserTags.SUBITEMCOMPLETED);
             Hashtable skippedSubItems = (Hashtable ) props.get(IParserTags.SUBITEMSKIPPED);
     
             ArrayList completedSubItemsItemList = new ArrayList ();
             ArrayList skippedSubItemsItemList = new ArrayList ();
     
             Enumeration e = completedSubItems.keys();
             while (e.hasMoreElements())
                 completedSubItemsItemList.add(e.nextElement());
     
             Enumeration e2 = skippedSubItems.keys();
             while (e2.hasMoreElements())
                 skippedSubItemsItemList.add(e2.nextElement());
     
             if (cid != null)
                 currentID = cid;
     
             if (itemNum >= 0) {
                 currentItemNum = itemNum;
                 
                 currentItem = getViewItemAtIndex(itemNum);
     
                 CheatSheetStopWatch.startStopWatch("CheatSheetViewer.checkSavedState()"); //$NON-NLS-1$
 for (int i = 0; i < viewItemList.size(); i++) {
     
                     ViewItem item = getViewItemAtIndex(i);
                     if (i > 0 && item.item.isDynamic() && i <= currentItemNum) {
                          item.handleButtons();
                          item.setOriginalColor();
                     }
     
                     if (completedStatesList.contains(Integer.toString(i))) {
                         item.setComplete();
                         item.setRestartImage();
                     } else {
                         if (i < currentItemNum) {
                             item.setSkipped();
                         }
                     }
                     if (expandedStatesList.contains(Integer.toString(i))) {
                         item.setExpanded();
                     } else {
                         item.setCollapsed();
                     }
                     if (i > currentItemNum) {
                         item.setButtonsVisible(false);
                         item.setCompletionMessageCollapsed();
                     } else {
                         item.setButtonsVisible(true);
                         if (i >currentItemNum || item.isCompleted()) {
                             item.setCompletionMessageExpanded(i + 1 >= viewItemList.size());
                         } else {
                             item.setCompletionMessageCollapsed();
                         }
                     }
                     if (expandRestoreList.contains(Integer.toString(i))) {
                         item.setCollapsed();
                     }
                     if (completedSubItemsItemList.contains(Integer.toString(i))) {
                         String subItemNumbers = (String ) completedSubItems.get(Integer.toString(i));
                         StringTokenizer st = new StringTokenizer (subItemNumbers, ","); //$NON-NLS-1$
 if (item instanceof CoreItem) {
                             CoreItem coreitemws = (CoreItem) item;
                             ArrayList subItemCompositeHolders = coreitemws.getListOfSubItemCompositeHolders();
                             if (subItemCompositeHolders != null) {
                                 while (st.hasMoreTokens()) {
                                     String token = st.nextToken();
                                     ((SubItemCompositeHolder) subItemCompositeHolders.get(Integer.parseInt(token))).setCompleted(true);
                                     ArrayList l = subItemCompositeHolders;
                                     SubItemCompositeHolder s = (SubItemCompositeHolder) l.get(Integer.parseInt(token));
                                     if (s != null && s.getStartButton() != null) {
                                         s.getStartButton().setImage(CheatSheetPlugin.getPlugin().getImage(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_RESTART));
                                         s.getStartButton().setToolTipText(Messages.RESTART_TASK_TOOLTIP);
                                     }
         
                                 }
                             }
                         }
                     }
                     if (skippedSubItemsItemList.contains(Integer.toString(i))) {
                         String subItemNumbers = (String ) skippedSubItems.get(Integer.toString(i));
                         StringTokenizer st = new StringTokenizer (subItemNumbers, ","); //$NON-NLS-1$
 if (item instanceof CoreItem) {
                             CoreItem coreitemws = (CoreItem) item;
                             while (st.hasMoreTokens()) {
                                 String token = st.nextToken();
                                 ((SubItemCompositeHolder) coreitemws.getListOfSubItemCompositeHolders().get(Integer.parseInt(token))).setSkipped(true);
                             }
                         }
                     }
                     CheatSheetStopWatch.printLapTime("CheatSheetViewer.checkSavedState()", "Time in CheatSheetViewer.checkSavedState() after loop #"+i+": "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }
                 CheatSheetStopWatch.printLapTime("CheatSheetViewer.checkSavedState()", "Time in CheatSheetViewer.checkSavedState() after loop: "); //$NON-NLS-1$ //$NON-NLS-2$

                 if (buttonIsDown) {
                     if(expandRestoreAction != null)
                         expandRestoreAction.setCollapsed(true);
                 }
                 
                 // If the last item is the current one and it is complete then
 // we should collapse the last item and set the focus on intro.
 // For all other cases, set the current item as the active item.
 if(viewItemList.size()-1 == itemNum && currentItem.isCompleted()) {
                     currentItem.setCollapsed();
                     getViewItemAtIndex(0).getMainItemComposite().setFocus();
                     
                     // The cheat sheet has been restored but is also completed so fire both events
 getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_RESTORED);
                     getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_COMPLETED);
                 } else {
                     currentItem.setAsCurrentActiveItem();
     
                     // If the intro item is completed, than the cheat sheet has been restored.
 if(getViewItemAtIndex(0).isCompleted())
                         getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_RESTORED);
                 }
     
                 /* LP-item event */
                 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, currentItem);
 } else {
                 getViewItemAtIndex(0).setAsCurrentActiveItem();
                 /* LP-item event */
                 // fireManagerItemEvent(ICheatSheetItemEvent.ITEM_ACTIVATED, items[0]);
 }
     
             return true;
         } catch(Exception e) {
             // An exception while restoring the saved state data usually only occurs if
 // the cheat sheet has been modified since this previous execution. This most
 // often occurs during development of cheat sheets and as such an end user is
 // not as likely to encounter this.

             boolean reset = MessageDialog.openConfirm(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                     Messages.CHEATSHEET_STATE_RESTORE_FAIL_TITLE,
                     Messages.CHEATSHEET_STATE_RESET_CONFIRM);

             if (reset) {
                 restart();
                 return true;
             }
             
             // Log the exception
 String stateFile = saveHelper.getStateFile(currentID).toOSString();
             String message = NLS.bind(Messages.ERROR_APPLYING_STATE_DATA_LOG, (new Object [] {stateFile, currentID}));
             IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, e);
             CheatSheetPlugin.getPlugin().getLog().log(status);

             // Set the currentID to null so it is not saved during internalDispose()
 currentID = null;

             internalDispose();

             // Reinitialize a few variables because there is no currentItem or currentPage now
 parserInput = null;
             currentItem = null;
             currentItemNum = -1;
             currentPage = null;
             expandRestoreList = new ArrayList ();
             viewItemList = new ArrayList ();
             
             // Create the errorpage to show the user
 createErrorPage(Messages.ERROR_APPLYING_STATE_DATA);

             return false;
         }
     }

     private void clearBackgrounds() {
         for (Iterator iter = viewItemList.iterator(); iter.hasNext();) {
             ViewItem item = (ViewItem) iter.next();
             item.setOriginalColor();
         }
     }

     private void clearIcons() {
         for (Iterator iter = viewItemList.iterator(); iter.hasNext();) {
             ViewItem item = (ViewItem) iter.next();
             item.setOriginalColor();
             if (item.isCompleted() || item.isExpanded() || item.isSkipped())
                     item.setIncomplete();
         }
     }

     private void collapseAllButCurrent(boolean fromAction) {
         expandRestoreList = new ArrayList ();
         try {
             ViewItem current = getViewItemAtIndex(currentItemNum);
             for (ListIterator iter = viewItemList.listIterator(viewItemList.size()); iter.hasPrevious();) {
                 ViewItem item = (ViewItem) iter.previous();
                 if (item != current && item.isExpanded()) {
                     item.setCollapsed();
                     if (fromAction)
                         expandRestoreList.add(Integer.toString(getIndexOfItem(item)));
                 }
             }
         } catch (Exception e) {
         }
     }

     private void collapseAllButtons() {
         for (Iterator iter = viewItemList.listIterator(1); iter.hasNext();) {
             ViewItem item = (ViewItem) iter.next();
             item.setButtonsVisible(false);
             item.setCompletionMessageCollapsed();
         }
     }

     private void createErrorPage(String message) {
         setCollapseExpandButtonEnabled(false);
         if(message != null) {
             currentPage = new ErrorPage(message);
         } else {
             currentPage = new ErrorPage();
         }
         currentPage.createPart(control);
         control.layout(true);
     }
     
     private void showStartPage() {
         setCollapseExpandButtonEnabled(false);
         internalDispose();

         howToBegin = new Label(control, SWT.WRAP);
         howToBegin.setText(Messages.INITIAL_VIEW_DIRECTIONS);
         howToBegin.setLayoutData(new GridData(GridData.FILL_BOTH));
         currentPage = null;
         control.layout(true);
     }
     
     private void createErrorPage(IStatus status) {
         setCollapseExpandButtonEnabled(false);
         currentPage = new ErrorPage(status);
         currentPage.createPart(control);
         control.layout(true);
     }

     /**
      * Creates the SWT controls for this workbench part.
      * <p>
      * Clients should not call this method (the workbench calls this method at
      * appropriate times).
      * </p>
      * <p>
      * For implementors this is a multi-step process:
      * <ol>
      * <li>Create one or more controls within the parent.</li>
      * <li>Set the parent layout as needed.</li>
      * <li>Register any global actions with the <code>IActionService</code>.</li>
      * <li>Register any popup menus with the <code>IActionService</code>.</li>
      * <li>Register a selection provider with the <code>ISelectionService</code>
      * (optional). </li>
      * </ol>
      * </p>
      *
      * @param parent the parent control
      */
     public void createPartControl(Composite parent) {
         control = new Composite(parent, SWT.NONE);
         GridLayout layout = new GridLayout();
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         layout.verticalSpacing = 0;
         layout.horizontalSpacing = 0;
         layout.numColumns = 1;
         control.setLayout(layout);

         control.addDisposeListener(new DisposeListener(){
             public void widgetDisposed(DisposeEvent e) {
                 dispose();
             }
         });
         
         showStartPage();

         Display display = parent.getDisplay();

         busyCursor = new Cursor(display, SWT.CURSOR_WAIT);

         if(contentElement != null) {
             initCheatSheetView();
         }
     }

     /**
      * Called when any TrayDialog is opened. The viewer must react by disabling
      * itself and moving the cheat sheet to the dialog's tray if the current item
      * was flagged as one that opens a modal dialog.
      *
      * @param dialog the dialog that was opened
      */
     private void dialogOpened(final TrayDialog dialog) {
         if (isActive()) {
             HelpTray tray = (HelpTray)dialog.getTray();
             if (tray == null) {
                 tray = new HelpTray();
                 dialog.openTray(tray);
             }
             ReusableHelpPart helpPart = tray.getHelpPart();
             IHelpPartPage page = helpPart.createPage(CheatSheetHelpPart.ID, null, null);
             page.setVerticalSpacing(0);
             page.setHorizontalMargin(0);
             ICheatSheetStateManager trayManager = new TrayStateManager();
             preTrayManager = stateManager;
             stateManager = trayManager;
             saveCurrentSheet(); // Save the state into the tray manager
 helpPart.addPart(CheatSheetHelpPart.ID, new CheatSheetHelpPart(helpPart.getForm().getForm().getBody(), helpPart.getForm().getToolkit(), page.getToolBarManager(), contentElement, trayManager));
             page.addPart(CheatSheetHelpPart.ID, true);
             helpPart.addPage(page);
             helpPart.showPage(CheatSheetHelpPart.ID);
             
             /*
              * Disable the viewer until the tray is closed, then show it again.
              */
             control.setVisible(false);
             Display.getCurrent().removeFilter(SWT.Show, listener);

             helpPart.getControl().addListener(SWT.Dispose, new Listener() {
                 public void handleEvent(Event event) {
                     control.setVisible(true);
                     Display.getCurrent().addFilter(SWT.Show, listener);
                     if (preTrayManager != null) {
                         loadState(); // Load from the tray manager
 stateManager = preTrayManager;
                         preTrayManager = null;
                     }
                     dialogReturnCode = dialog.getReturnCode();
                 }
             });
         }
     }

     /**
      * Disposes of this cheat sheet viewer.
      */
     private void dispose() {
         internalDispose();
         if (busyCursor != null)
             busyCursor.dispose();
     }

     /*
      * Returns the cheat sheet being viewed.
      */
     public ICheatSheet getCheatSheet() {
         return model;
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.ui.cheatsheets.ICheatSheetViewer#getCheatSheetID()
      */
     public String getCheatSheetID() {
         if(getContent() != null) {
             return getContent().getID();
         }
         
         return null;
     }

     /**
      * Returns the current content.
      *
      * @return CheatSheetElement
      */
     /*package*/ CheatSheetElement getContent() {
         return contentElement;
     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.cheatsheets.ICheatSheetViewer#getControl()
      */
     public Control getControl() {
         return control;
     }

     private int getIndexOfItem(ViewItem item) {
         int index = viewItemList.indexOf(item);
         if(index != -1) {
             return index;
         }
         return 0;
     }

     /*package*/ CheatSheetManager getManager() {
         if (manager == null) {
             getNewManager();
         }
         return manager;
     }

     private CheatSheetManager getNewManager(){
         manager = new CheatSheetManager(contentElement);
         return manager;
     }
     
     private CheatSheetManager initManager(){
         CheatSheetManager csManager = getManager();
         csManager.setData(new Hashtable ());
         return csManager;
     }

     private ViewItem getViewItemAtIndex(int index) {
         if (viewItemList != null && !viewItemList.isEmpty()) {
             return (ViewItem) viewItemList.get(index);
         }
         return null;
     }
     
     /**
      * Returns whether or not this viewer contains the given Control, which
      * is currently in focus.
      *
      * @param control the Control currently in focus
      * @return whether this viewer contains the given Control or not
      */
     public boolean hasFocusControl(Control control) {
         return (control == this.control) || (currentPage.getControl() == control);
     }
     
     /**
      * If in a dialog-opening step, will add the appropriate listener for
      * the cheatsheet to jump into the dialog's tray once opened.
      *
      * Should be called before executing any action.
      */
     private void hookDialogListener() {
         /*
          * org.eclipse.help.ui is an optional dependency; only perform this
          * step is this plugin is present.
          */
         if (!inDialog && isInDialogItem() && (Platform.getBundle("org.eclipse.help.ui") != null)) { //$NON-NLS-1$
 listener = new Listener() {
                 public void handleEvent(Event event) {
                     if (isTrayDialog(event.widget)) {
                         dialogOpened((TrayDialog)((Shell)event.widget).getData());
                     }
                 }
             };
             Display.getCurrent().addFilter(SWT.Show, listener);
         }
     }
     
     /**
      * Removes the dialog-opening listener, if it was added.
      *
      * Should be called after executing any action.
      */
     private void unhookDialogListener() {
         if (listener != null) {
             Display.getCurrent().removeFilter(SWT.Show, listener);
         }
     }
     
     /*
      * return true if a cheat sheet was opened successfully
      */
     private boolean initCheatSheetView() {
         CheatSheetStopWatch.startStopWatch("CheatSheetViewer.initCheatSheetView()"); //$NON-NLS-1$
 //Re-initialize list to store items collapsed by expand/restore action on c.s. toolbar.
 expandRestoreList = new ArrayList ();

         // re set that action to turned off.
 if(expandRestoreAction != null)
             expandRestoreAction.setCollapsed(false);

         //reset current item to be null; next item too.
 currentItem = null;
         currentItemNum = 0;
         viewItemList = new ArrayList ();

         // Reset the page variable
 currentPage = null;
         
         if(howToBegin != null) {
             howToBegin.dispose();
             howToBegin = null;
         }
         
         // If a null cheat sheet id was specified, return leaving the cheat sheet empty.
 if(nullCheatSheetId) {
             return false;
         }
         
         if(invalidCheatSheetId) {
             createErrorPage(Messages.ERROR_CHEATSHEET_DOESNOT_EXIST);
             return false;
         }
         
         // read our contents, if there are problems reading the file an error page should be created.
 CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() before readFile() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 IStatus parseStatus = readFile();
         CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after readFile() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 if (!parseStatus.isOK()) {
             CheatSheetPlugin.getPlugin().getLog().log(parseStatus);
         }
         if(parseStatus.getSeverity() == Status.ERROR){

             // Error during parsing.
 // Something is wrong with the Cheat sheet content file at the xml level.

             createErrorPage(parseStatus);
             return false;
         }
         
         control.setRedraw(false);
         if (model instanceof CheatSheet) {
             CheatSheet cheatSheetModel = (CheatSheet)model;

             if (isRestricted && cheatSheetModel.isContainsCommandOrAction()) {
                 boolean isOK = MessageDialog.openConfirm(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                         Messages.CHEATSHEET_FROM_URL_WITH_EXEC_TITLE,
                         Messages.CHEATSHEET_FROM_URL_WITH_EXEC);

                 if (!isOK) {
                     control.setRedraw(true);
                     showStartPage();
                     return true;
                 }
             }
             
             currentPage = new CheatSheetPage(cheatSheetModel, viewItemList, this);
             setCollapseExpandButtonEnabled(true);
         } else if (model instanceof CompositeCheatSheetModel) {
             CompositeCheatSheetModel compositeCheatSheetModel = ((CompositeCheatSheetModel)model);
             compositeCheatSheetModel.setId(currentID);
             currentPage = new CompositeCheatSheetPage(compositeCheatSheetModel, stateManager);
             compositeCheatSheetModel.setCheatSheetManager(initManager());
             setCollapseExpandButtonEnabled(false);
         }
         CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after CheatSheetPage() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 currentPage.createPart(control);
         CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after CheatSheetPage.createPart() call: "); //$NON-NLS-1$ //$NON-NLS-2$

         if (model instanceof CheatSheet) {
             CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after fireEvent() call: "); //$NON-NLS-1$ //$NON-NLS-2$

             if(!loadState()) {
                 // An error occurred when apply the saved state data.
 control.setRedraw(true);
                 control.layout();
                 return true;
             }

             getManager().fireEvent(ICheatSheetEvent.CHEATSHEET_OPENED);
         }
         CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after checkSavedState() call: "); //$NON-NLS-1$ //$NON-NLS-2$

         currentPage.initialized();
         control.setRedraw(true);
         control.layout();
         CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() after layout() call: "); //$NON-NLS-1$ //$NON-NLS-2$

         if (currentItem != null && !currentItem.isCompleted())
             currentItem.setFocus();
         CheatSheetStopWatch.printLapTime("CheatSheetViewer.initCheatSheetView()", "Time in CheatSheetViewer.initCheatSheetView() at end of method: "); //$NON-NLS-1$ //$NON-NLS-2$
 return true;
     }

     private void internalDispose() {
         if(manager != null)
             manager.fireEvent(ICheatSheetEvent.CHEATSHEET_CLOSED);
        
         saveCurrentSheet();

         for (Iterator iter = viewItemList.iterator(); iter.hasNext();) {
             ViewItem item = (ViewItem) iter.next();
             item.dispose();
         }

         if(currentPage != null) {
             currentPage.dispose();
         }
     }

     /**
      * Returns whether or not the cheat sheet viewer is currently active. This
      * means it is visible to the user and enabled.
      *
      * @return whether or not this viewer is active
      */
     private boolean isActive() {
         Control control = getControl();
         if (control != null && !control.isDisposed()) {
             Control parent = control.getParent();
             return (parent != null && !parent.isDisposed() && parent.isVisible() && parent.isEnabled());
         }
         return false;
     }
     
     /*
      * Show the collapse/expand button if we have access to the toolbar
      */
     private void setCollapseExpandButtonEnabled(boolean enable) {
         if (expandRestoreAction != null) {
             expandRestoreAction.setEnabled(enable);
         }
     }

     /**
      * Returns whether or not the currently active item requires opening a
      * modal dialog.
      *
      * @return whether the current item opens a modal dialog
      */
     private boolean isInDialogItem() {
         ViewItem item = getViewItemAtIndex(currentItemNum);
         if (item != null) {
             return item.getItem().isDialog();
         }
         return false;
     }
     
     /**
      * Returns whether or not this cheat sheet viewer is inside a modal
      * dialog.
      *
      * @return whether this viewer is inside a modal dialog
      */
     public boolean isInDialogMode() {
         return inDialog;
     }
     
     /**
      * Returns whether the given widget is a TrayDialog.
      *
      * @param widget the widget to check
      * @return whether or not the widget is a TrayDialog
      */
     private boolean isTrayDialog(Widget widget) {
         return (widget instanceof Shell && ((Shell)widget).getData() instanceof TrayDialog);
     }

     /**
     * Read the contents of the cheat sheet file
     * @return true if the file was read and parsed without error
     */
    private IStatus readFile() {
        if(parser == null)
            parser = new CheatSheetParser();
        // If the cheat sheet was registered then
 // search for a specific type - composite or simple
 int cheatSheetKind = CheatSheetParser.ANY;
        if (contentElement.isRegistered()) {
            if (contentElement.isComposite()) {
                cheatSheetKind = CheatSheetParser.COMPOSITE_ONLY;
            } else {
                cheatSheetKind = CheatSheetParser.SIMPLE_ONLY;
            }
        }
    
        model = parser.parse(parserInput, cheatSheetKind);
        return parser.getStatus();
    }

    private void restoreExpandStates() {
        try {
            for (int i = 0; i < expandRestoreList.size(); i++) {
                int index = Integer.parseInt(((String ) expandRestoreList.get(i)));
                ViewItem item = getViewItemAtIndex(index);
                if (!item.isExpanded()) {
                    item.setExpanded();
                }
            }
            expandRestoreList = null;
        } catch (Exception e) {
        }
    }
    
    /*package*/ void runPerformExecutable(ImageHyperlink link) {
        link.setCursor(busyCursor);
        currentItem = (ViewItem) link.getData();
        CoreItem coreItem = (CoreItem) currentItem;

        if (coreItem != null) {
            try {
                hookDialogListener();
                dialogReturnCode = -1;
                IStatus status = coreItem.runExecutable(getManager());
                if (status.isOK() && dialogReturnCode != Window.CANCEL) {
                    coreItem.setRestartImage();
                    if (!coreItem.hasConfirm()) {
                        //set that item as complete.
 advanceItem(link, true);
                        saveCurrentSheet();
                    }
                }
                if ( status.getSeverity() == IStatus.ERROR) {
                    CheatSheetPlugin.getPlugin().getLog().log(status);
                    org.eclipse.jface.dialogs.ErrorDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), null, null, status);
                }
            }
            finally {
                unhookDialogListener();
            }
        }

        link.setCursor(null);
    }

    /*package*/ void runSubItemPerformExecutable(ImageHyperlink link, int subItemIndex) {
        CoreItem coreItem = null;
        link.setCursor(busyCursor);
        currentItem = (ViewItem) link.getData();
        coreItem = (CoreItem) currentItem;

        try {
            if (coreItem != null) {
                hookDialogListener();
                if (coreItem.runSubItemExecutable(getManager(), subItemIndex) == ViewItem.VIEWITEM_ADVANCE && !coreItem.hasConfirm(subItemIndex)) {
                    ArrayList l = coreItem.getListOfSubItemCompositeHolders();
                    SubItemCompositeHolder s = (SubItemCompositeHolder) l.get(subItemIndex);
                    s.getStartButton().setImage(CheatSheetPlugin.getPlugin().getImage(ICheatSheetResource.CHEATSHEET_ITEM_BUTTON_RESTART));
                    s.getStartButton().setToolTipText(Messages.RESTART_TASK_TOOLTIP);
                    advanceSubItem(link, true, subItemIndex);
                    saveCurrentSheet();
                }
            }
        } catch (RuntimeException e) {
            IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, Messages.ERROR_RUNNING_ACTION, e);
            CheatSheetPlugin.getPlugin().getLog().log(status);
            org.eclipse.jface.dialogs.ErrorDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), null, null, status);
        } finally {
            unhookDialogListener();
            link.setCursor(null);
        }
    }

    /*package*/ void saveCurrentSheet() {
        if(currentID != null) {
            if (currentPage instanceof CheatSheetPage) {
                Properties properties = saveHelper.createProperties(currentItemNum, viewItemList, getExpandRestoreActionState(), expandRestoreList, currentID, restorePath);
                IStatus status = stateManager.saveState(properties, getManager());
                if (!status.isOK()) {
                    CheatSheetPlugin.getPlugin().getLog().log(status);
                }
            } else if (currentPage instanceof CompositeCheatSheetPage) {
                ((CompositeCheatSheetPage)currentPage).saveState();
            }
        }
    }
    
    private boolean getExpandRestoreActionState() {
        boolean expandRestoreActionState = false;
        if(expandRestoreAction != null)
            expandRestoreActionState = expandRestoreAction.isCollapsed();
        return expandRestoreActionState;
    }

    /*package*/ void setContent(CheatSheetElement element, ICheatSheetStateManager inputStateManager) {
        CheatSheetStopWatch.startStopWatch("CheatSheetViewer.setContent(CheatSheetElement element)"); //$NON-NLS-1$

        // Cleanup previous contents
 internalDispose();

        // Set the current content to new content
 contentElement = element;
        stateManager = inputStateManager;
        stateManager.setElement(element);

        currentID = null;
        parserInput = null;
        if (element != null) {
            initInputFields(element);
        }

        CheatSheetStopWatch.printLapTime("CheatSheetViewer.setContent(CheatSheetElement element)", "Time in CheatSheetViewer.setContent() before initCheatSheetView() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 // Initialize the view with the new contents
 boolean cheatSheetOpened = false;
        if (control != null) {
            cheatSheetOpened = initCheatSheetView();
        }
        if (!cheatSheetOpened) {
            contentElement = null;
            stateManager = null;
        }
        // If the cheat sheet failed to open clear the content element so we don't see an
 CheatSheetStopWatch.printLapTime("CheatSheetViewer.setContent(CheatSheetElement element)", "Time in CheatSheetViewer.setContent() after initCheatSheetView() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 }

    private void initInputFields(CheatSheetElement element) {
        currentID = element.getID();
        String contentXml = element.getContentXml();
        URL contentURL = null;
        restorePath = element.getRestorePath();
        
        if (contentXml != null) {
            parserInput = new ParserInput(contentXml, element.getHref());
            return;
        }

        // The input was not an XML string, find the content URL
 Bundle bundle = null;
        if(element != null && element.getConfigurationElement() != null)
            try{
                String pluginId = element.getConfigurationElement().getContributor().getName();
                bundle = Platform.getBundle(pluginId);
            } catch (Exception e) {
                // do nothing
 }
        if (bundle != null) {
            contentURL = FileLocator.find(bundle, new Path(element.getContentFile()), null);
        }

        if (contentURL == null) {
            try {
                contentURL = new URL (element.getHref());
            } catch (MalformedURLException mue) {
            }
        }
        parserInput = new ParserInput(contentURL, bundle != null ? bundle.getSymbolicName() : null);
    }
    
    
    /*package*/ void setExpandRestoreAction(CheatSheetExpandRestoreAction action) {
        expandRestoreAction = action;
    }

    /**
     * Passing the focus request to the viewer's control.
     */
    public void setFocus() {
        //need this to have current item selected. (Assumes that when you reactivate the view you will work with current item.)
 if (currentItem != null) {
            currentItem.setFocus();
        } else {
            getControl().setFocus();
        }
    }
    

    /* (non-Javadoc)
     * @see org.eclipse.ui.cheatsheets.ICheatSheetViewer#setInput(java.lang.String)
     */
    public void setInput(String id) {
        setInput(id, new DefaultStateManager());
    }

    public void setInput(String id, ICheatSheetStateManager inputStateManager) {
        CheatSheetStopWatch.startStopWatch("CheatSheetViewer.setInput(String id)"); //$NON-NLS-1$

        CheatSheetElement element = null;

        if(id == null) {
            nullCheatSheetId = true;
        } else {
            nullCheatSheetId = false;

            element = CheatSheetRegistryReader.getInstance().findCheatSheet(id);
            if(element == null) {
                String message = NLS.bind(Messages.ERROR_INVALID_CHEATSHEET_ID, (new Object [] {id}));
                IStatus status = new Status(IStatus.ERROR, ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID, IStatus.OK, message, null);
                CheatSheetPlugin.getPlugin().getLog().log(status);
                invalidCheatSheetId = true;
            } else {
                invalidCheatSheetId = false;
                this.isRestricted = false;
            }
        }

        CheatSheetStopWatch.printLapTime("CheatSheetViewer.setInput(String id)", "Time in CheatSheetViewer.setInput(String id) before setContent() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 setContent(element, inputStateManager);
        CheatSheetStopWatch.printLapTime("CheatSheetViewer.setInput(String id)", "Time in CheatSheetViewer.setInput(String id) after setContent() call: "); //$NON-NLS-1$ //$NON-NLS-2$

        // Update most recently used cheat sheets list.
 CheatSheetPlugin.getPlugin().getCheatSheetHistory().add(element);
        CheatSheetStopWatch.printLapTime("CheatSheetViewer.setInput(String id)", "Time in CheatSheetViewer.setInput(String id) after getCheatSheetHistory() call: "); //$NON-NLS-1$ //$NON-NLS-2$
 }

    /* (non-Javadoc)
     * @see org.eclipse.ui.cheatsheets.ICheatSheetViewer#setInput(java.lang.String, java.lang.String, java.net.URL)
     */
    public void setInput(String id, String name, URL url) {
        setInput(id, name, url, new DefaultStateManager(), false);
    }
    
    public void setInputFromXml(String id, String name, String xml, String basePath) {
        if (id == null || name == null || xml == null) {
            throw new IllegalArgumentException ();
        }
        CheatSheetElement element = new CheatSheetElement(name);
        element.setID(id);
        element.setContentXml(xml);
        element.setHref(basePath);

        nullCheatSheetId = false;
        invalidCheatSheetId = false;
        isRestricted = false;
        setContent(element, new NoSaveStateManager());
    }

    public void setInput(String id, String name, URL url,
            ICheatSheetStateManager inputStateManager, boolean isRestricted) {
        if (id == null || name == null || url == null) {
            throw new IllegalArgumentException ();
        }
        CheatSheetElement element = new CheatSheetElement(name);
        element.setID(id);
        element.setHref(url.toString());

        nullCheatSheetId = false;
        invalidCheatSheetId = false;
        this.isRestricted = isRestricted;
        setContent(element, inputStateManager);
    }
    
    /*package*/ void toggleExpandRestore() {
        if(expandRestoreAction == null)
            return;

        if (expandRestoreAction.isCollapsed()) {
            restoreExpandStates();
            expandRestoreAction.setCollapsed(false);
        } else {
            collapseAllButCurrent(true);
            expandRestoreAction.setCollapsed(true);
        }

    }

    public Action getCopyAction() {
        return copyAction;
    }

    public void setCopyAction(Action copyAction) {
        this.copyAction = copyAction;
    }
    
    public void copy() {
        if (currentItem!=null)
            currentItem.copy();
    }

    public void addListener(CheatSheetListener listener) {
        if (contentElement != null ) {
            getManager().addListener(listener);
        }
    }

    public int contributeToViewMenu(Menu menu, int index) {
        if (currentPage instanceof IMenuContributor) {
            return ((IMenuContributor)currentPage).contributeToViewMenu(menu, index);
        }
        return index;
    }
    
    public void restart() {
        resetItemState();
        currentItemNum = 0;
        collapseAllButCurrent(false);
        IntroItem introItem = (IntroItem) getViewItemAtIndex(0);
        introItem.setIncomplete();
        showIntroItem();
    }

    public void saveState(IMemento memento) {
        if (currentPage instanceof CheatSheetPage) {
            Properties properties = saveHelper.createProperties(currentItemNum, viewItemList, getExpandRestoreActionState(), expandRestoreList, currentID, restorePath);
            saveHelper.saveToMemento(properties, getManager(), memento);
        }
    }

    public void reset(Map cheatSheetData) {
        if (currentPage instanceof CheatSheetPage) {
            restart();
            getManager().setData(cheatSheetData);
        } else if (currentPage instanceof CompositeCheatSheetPage) {
            ((CompositeCheatSheetPage)currentPage).restart(cheatSheetData);
        }
    }
    
    public void showError(String message) {
        internalDispose();
        if(howToBegin != null) {
            howToBegin.dispose();
            howToBegin = null;
        }
        createErrorPage(message);
    }

}
